Loops are used for repeating similar actions multiple times. for loops iterate over a set of values. The iterator (i) changes with every iteration of the loop:
for(i inc(1,2,3)) print(i)
[1] 1
[1] 2
[1] 3
To generate sequences of integers, we can use seq_len. Let’s make a function:
print_repetitions <-function(n) {for (i inseq_len(n)) { print(i) }}
print_repetitions(2)
[1] 1
[1] 2
Control structures – while loops
while loops repeat a task until a condition is no longer met.
add_until_4 <-function(x) {while(x <4) { x <- x +1print(x) }}
add_until_4(1)
[1] 2
[1] 3
[1] 4
Exercise 1 - Function for latitudinal binning
Create a function that can sort a data.frame into latitudinal bins. That is, we want a new column that identifies the bin of each entry of the data set. As an exemplary data set, we can use the reefs data from palaeoverse.
If you are new to writing R functions, try a simpler function that can sort data into the northern and southern hemisphere.
Here is what the result may look like when sorted into hemispheres:
reefs[72:73, c("name", "lat", "hemisphere")]
name lat hemisphere
72 Begunjscica 46.4333 north
73 W-Ceram -3.2500 south
Exercise 1 - My solution
Best practices
Naming style
Give variables and functions consistent names. These are the two most common styles:
Good code shouldn’t need a large amount of comments - but comment enough that you can still use your code two years later.
Comments
Add some general information in the beginning of a large R script.
### Change point regression analysis### July 2021### Kilian Eichenseer###### Bayesian algorithm for finding a change point in ### the linear relationship between two variables. ### Uses JAGS (https://mcmc-jags.sourceforge.io/).### Generate dataset.seed(10) n <-60# total number of data points
Even better: add formal documentation.
Documentation
?mean
roxygen2
R package, install with install.packages("roxygen2")
Used to create documentation for R packages (functions, data, …)
Start every line of documentation with ’#
This could generate documentation for the subtract function from earlier:
#' Subtraction#' #' Subtracts `arg2` from `arg1`#'#' @param arg1 `Numeric`. First argument.#' @param arg2 `Numeric`. Second argument.#' @return A `numeric` containing the difference #' between `arg1` and `arg2`.#' @examples#' subtract(2,1)
?subtract
Exercise 2 - Document your function
Make sure your code follows the same style throughout
Add in-line comments if necessary
Create documentation with roxygen2
Exercise 2 - Create a package
From RStudio, create a New Directory (File -> New Project...)
Exercise 2 - Create a package
Exercise 2 - Create a package
Save functions in the R folder as .R files
Documentation will be automatically created in the man folder
You may need to delete the NAMESPACE file once to avoid warnings
Exercise 2 - use roxygen2
We start by installing roxygen2 and loading it:
install.packages("roxygen2")library("roxygen2")
To generate documentation from our roxygen2 comments, which are denoted by the #' tags, run
roxygenise()
or press Ctrl + Shift + D.
We can now read the documentation of our function by calling
?my_function # use the name of your documented function
Exercise 2 - My solution
Testing and Debugging
Error handling
Very helpful in complex functions
subtract(2, 1)
[1] 1
subtract("2", 1)
Error in arg1 - arg2: non-numeric argument to binary operator
Check that input is correct and display custom error messages:
subtract <-function(arg1, arg2) {if (is.numeric(arg1) ==FALSE) {stop("`arg1` must be numeric") } arg1 - arg2}
subtract("2", 1)
Error in subtract("2", 1): `arg1` must be numeric
tryCatch
Use if you anticipate an error but want function to continue.
Let’s try to generate data from a multivariate normal distribution:
mvnfast::rmvn(1, mu =c(0,0), sigma =matrix(rep(1,4), 2))
Error in mvnfast::rmvn(1, mu = c(0, 0), sigma = matrix(rep(1, 4), 2)): chol(): decomposition failed
mvnfast::rmvn is fast but fails for some problematic sigma values. In case it fails, we use MASS::mvrnorm instead:
my_rmvn(n =1, mu =c(0,0), sigma =matrix(rep(1,4), 2))
[1] -0.7513473 -0.7513473
Traceback
If something went wrong, find out where using traceback():
my_rmvn(n =1, mu =0, sigma =matrix(rep(1,4), 2))
Error in MASS::mvrnorm(n, mu, sigma): incompatible arguments
traceback()
Break points
Break points allow you to look inside your function’s environment. Click next to a line of code in your function to activate a break point (a red dot appears):
Now run the function…
my_rmvn(n =1, mu =c(0,0), sigma =matrix(rep(1,4), 2))
You may need to run devtools::load_all() first.
Break points
Break points
We can now browse the function environment in the console like we normally can browse the global environment. For example we can look at sigma:
Press Stop to end the browsing. Don’t forget to deactivate the break point by clicking on the red dot in the script.
Tests
Functions should be tested before they are used.
Sometimes, interactive testing may be enough.
For example, identical() tests whether two objects are exactly equal:
identical(subtract(2,1),1)
[1] TRUE
Automated testing with the testthat R package is superior.
testthat
Used to create unit tests for R packages that can be run automatically
R package, install with install.packages("testthat")
Set up infrastructure for a test with usethis::test_name
Create a test file for the subtract function:
usethis::use_test("subtract")
This has created a test-subtract.R file in package_name/tests/testthat/
testthat
In the test-subtract.R file, we can write tests, for example:
expect_snapshot (for results that are difficult to describe with code)
The expect_doppelganger() function from the vdiffr package is good for testing plots:
test_that("plot function works", { plot_1 <-function() plot(1,2) vdiffr::expect_doppelganger("plot_1", plot_1)
This will create an image in the tests/testthat/_snaps/function_name directory. Upon first calling this, inspect the image to see if it is as expected. Future tests will fail if the function call in the test changes the image.
Comments
Good code shouldn’t need a large amount of comments - but comment enough that you can still use your code two years later.